class Dictionary {
    constructor (toStrFn = defaultToString) {
      this.toStrFn = toStrFn
      this.table = {}
    }


    hasKey (key) {
        let value = this.table[this.toStrFn(key)]
        return value !== null && value != undefined
    }

    set (key, value) {
        if (key != null && value != null) {
          const tableKey = this.toStrFn(key)
          this.table[tableKey] = new ValuePair(key, value)
          return true
        }
        return false
    }

    remove (key) {
        if (this.hasKey(key)) {
          delete this.table[this.toStrFn(key)]
          return true
        }
        return false
    }

    get (key) {
        const valuePair = this.table[this.toStrFn(key)]
        return valuePair == null ? undefined : valuePair.value
    }

    keyValues () {
        return Object.values(this.table)
    }
    keys () {
        return this.keyValues().map(valuePair => valuePair.key)
    }
    values () {
        return this.keyValues().map(valuePair => valuePair.value)
    }

    forEach (callback) {
        const valuePairs = this.keyValues()
        for (let index = 0; index < valuePairs.length; index++) {
            const result = callback(valuePair.key, valuePair.value)
            if (result === false) {
                break
            }
        }
    }

    size () {
        return this.keyValues(this.table).length
    }
    isEmpty () {
        return this.size() === 0
    }
    clear () {
        this.table = {}
    }
    toString () {
        if (this.isEmpty()) {
            return ''
        }
        const valuePairs = this.keyValues()
        let objStr = valuePairs[0].toString()
        for (let index = 1; index < valuePairs.length; index++) {
          objStr = `${objStr}, ${valuePairs[index].toString()}`
        }
        return objStr
    }
}

function defaultToString (item) {
    if (item === null) {
      return `NULL`
    } else if (item === undefined) {
      return 'UNDEFINED'
    } else if (typeof item === 'string' || item instanceof String) {
      return `${item}`
    }
    return item.toString()
}

class ValuePair {
    constructor (key, value) {
      this.key = key
      this.value = value
    }
    toString () {
      return `{#${this.key}}: ${this.value}`
    }
}



const dic = new Dictionary()
dic.set('AAA', 'AAA@qq.com')
dic.set('BBB', 'BBB@163.com')
dic.set('CCC', 'CCC@gmail.com')
console.log(dic.hasKey('AAA'))  // true
console.log(dic.size())         // 3
console.log(dic.get('AAA'))     // AAA@qq.com
console.log(dic.keys())         // ['AAA', 'BBB', 'CCC']
console.log(dic.values())       // ['AAA@qq.com', 'BBB@163.com', 'CCC@gmail.com']
console.log(dic.keyValues())    // [{key: 'AAA', value: 'AAA@qq.com'}， {key: 'BBB', value: 'BBB@qq.com'}， {key: 'CCC', value: 'CCC@qq.com'}]
dic.remove('BBB')
console.log(dic.keys())         // ['AAA', 'CCC']
console.log(dic.values())       // ['AAA@qq.com',  'CCC@gmail.com']
console.log(dic.keyValues())    // [{key: 'AAA', value: 'AAA@qq.com'}， {key: 'CCC', value: 'CCC@qq.com'}]
dic.forEach((key, value) => {
  console.log('forEach: ', `key: ${key}, value: ${value}`)
  // forEach: key: AAA, value: AAA@qq.com
  // forEach: key: CCC, value: CCC@gmail.com
})